Option Explicit

Private Const TEST_TABLE_NAME As String = "table1"
Private Const TEST_POINT As String = "."
Private Const TEST_COL_NAME As String = "col1"
Private Const TEST_SHARE_LINE_COMMENT As String = "--aaaaaa"
Private Const TEST_SINGLE_LINE_COMMENT As String = "--bbbbb"

' ======================================================================
' 执行所有测试用例
' 这里只有格式化器的解析器中的辅助方法的测试
' ======================================================================
Public Sub test_all_case()
    ' 测试: 获取Not开头的条件运算符
    test_getNotConditionOp
    ' 测试: 为一个token吸收一个同行注释并合并为TokenGroup
    test_consumeShareLineCommentToTokenGroup
    ' 测试: 将左括号栈回退到扫描器内
    test_putBackStackToScanner
    ' 测试: 解析开头连续出现的左括号
    test_parseStartLeftParenthesis
    ' 测试: 解析开头连续出现的右括号
    test_parseStartRightParenthesis
    ' 测试: 合并两个with作用域
    test_saveWithEnvGroup
    ' 测试: 合并with和其他类型作用域 (合并成一个容器作用域)
    test_mergeWithEnv
    ' 测试: 将两个作用域保存到容器作用域 (合并时，将会利用参数中已有的容器作用域)
    test_containEnvs
    ' 测试: 生成union关键字
    test_parseUnionKey
    ' 测试: 吸收union关键字到作用域
    test_mergeKeyUnion
    ' 测试: 吸收一个token到作用域右侧
    test_consumeTokenToEnvRight
    ' 测试: 吸收多余的左括号栈到作用域左侧
    test_consumeLeftParenthesisStackAllTokenToEnv
    ' 测试: 吸收一个同行注释到作用域右侧
    test_consumeOneShareLineCommentToEnvRight
    ' 测试: 吸收多个元素到作用域右侧
    test_consumeComponentToEnvRight
    ' 测试: 保证作用域的可添加性
    test_ensureComponentAdditivityEnv
    ' 吸收左括号栈栈顶连续出现的注释到作用域左侧
    test_consumeLPStackTopCommentToEnvLeft
    ' 吸收左括号栈栈顶连续出现的单行注释到作用域左侧
    test_consumeLPStackTopSingleLineCommentToEnvLeft
    ' 测试: 吸收右侧连续出现的注释到作用域
    test_consumeCommentToEnvRight
    ' 测试: 作用域括号配对
    test_matchEnvBoundaryParenthesis
    ' 测试: 将栈中的所有内容保存到作用域末尾
    test_appendStackToEnv
    ' 测试: 将栈中的所有内容导出到一个容器作用域
    test_clearStackToContainerEnv
    ' 测试: 保证列对象的可添加性
    test_ensureComponentAdditivityCol
    ' 测试: 吸收一个token到列对象右侧
    test_consumeTokenToColRight
    ' 测试: 吸收一个同行注释到列对象右侧
    test_consumeOneShareLineCommentToColRight
    ' 测试: 吸收多个元素到列对象右侧
    test_consumeComponentToColRight
    ' 测试: 从左括号栈吸收多个元素到列对象左侧
    test_consumeComponentFromStackToColLeft
    ' 吸收左括号栈栈顶连续出现的注释到列对象左侧
    test_consumeLPStackTopCommentToColLeft
    ' 测试: 吸收右侧连续出现的注释到列对象
    test_consumeCommentToColRight
    ' 测试: 对一个右括号进行配对并生成容器列对象
    test_matchOneRightParenthesis
    ' 测试: 列对象括号配对
    test_matchColBoundaryParenthesis
    ' 测试: 吸收多余的左括号栈到列对象左侧
    test_consumeLeftParenthesisStackAllTokenToCol
    ' 测试: 将栈中的所有内容保存到列对象末尾
    test_appendStackToCol
    ' 测试: 将栈中的所有内容导出到一个列对象
    test_clearStackToContainerCol
    ' 测试: 将两个元素合并为多条件组合对象
    test_mergeTwoElemToMultiConditions
    ' 测试: 创建一个只有逻辑运算符和第一个元素的的条件
    test_createOneElemCondition
    ' 测试: 创建一个IS_NOT组合关键字
    test_createIsNotConditionOp
End Sub

' ======================================================================
' 测试通用处理
' ======================================================================

' 创建一个格式化器的 sql 解析器
Private Function createSqlFormatterAnalyzer() As SqlFormatterAnalyzer
    Dim config As SqlFormatterConfiguration
    Set config = New SqlFormatterConfiguration

    Dim result As SqlFormatterAnalyzer
    Set result = New SqlFormatterAnalyzer

    result.Init config

    Set createSqlFormatterAnalyzer = result
End Function

' 创建一个扫描器
Private Function getObjArrayRefScanner(ParamArray tokens() As Variant) As ObjArrayRefScanner
    Dim i As Integer
    Dim t As SqlToken

    Dim tokenCount As Integer
    tokenCount = UBound(tokens)

    ' 创建一个和参数数量相同的数组引用
    Dim meanTokens As IObjArrayRef
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(tokenCount)

    ' 遍历参数，并保持到数组引用
    For i = 0 To UBound(tokens)
        Set t = tokens(i)

        meanTokens.Add t
    Next

    ' 创建扫描器
    Dim scanner As ObjArrayRefScanner
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' return scanner
    Set getObjArrayRefScanner = scanner
End Function

' 获取 with 关键字
Private Function getWithToken() As SqlToken
    Set getWithToken = New SqlToken
    getWithToken.Init "with", SqlTokenType.KEY_WITH
End Function

' 获取 select 关键字
Private Function getSelectToken() As SqlToken
    Set getSelectToken = New SqlToken
    getSelectToken.Init "select", SqlTokenType.KEY_SELECT
End Function

' + 运算符
Private Function getPlusToken() As SqlToken
    Set getPlusToken = New SqlToken
    getPlusToken.Init "+", SqlTokenType.OPERA_PLUS
End Function

' 获取 distinct 关键字
Private Function getDistinctToken() As SqlToken
    Set getDistinctToken = New SqlToken
    getDistinctToken.Init "distinct", SqlTokenType.KEY_DISTINCT
End Function


' 创建一个数值元素
Private Function createNumToken(ByVal v As String) As SqlToken
    Set createNumToken = New SqlToken
    createNumToken.Init v, SqlTokenType.STR_NUM
End Function

' 获取 * 元素
Private Function getAsteriskToken() As SqlToken
    Set getAsteriskToken = New SqlToken
    getAsteriskToken.Init "*", SqlTokenType.ASTERISK
End Function

' 获取 offset 关键字
Private Function getOffsetToken() As SqlToken
    Set getOffsetToken = New SqlToken
    getOffsetToken.Init "offset", SqlTokenType.KEY_OFFSET
End Function

' 获取 limit 关键字
Private Function getLimitToken() As SqlToken
    Set getLimitToken = New SqlToken
    getLimitToken.Init "limit", SqlTokenType.KEY_LIMIT
End Function

' 获取 is 关键字
Private Function getIsToken() As SqlToken
    Set getIsToken = New SqlToken
    getIsToken.Init "is", SqlTokenType.KEY_IS
End Function

' 获取 not 关键字
Private Function getNotToken() As SqlToken
    Set getNotToken = New SqlToken
    getNotToken.Init "not", SqlTokenType.KEY_NOT
End Function

' 获取 union 关键字
Private Function getUnionToken() As SqlToken
    Set getUnionToken = New SqlToken
    getUnionToken.Init "union", SqlTokenType.KEY_UNION
End Function

' 获取 all 关键字
Private Function getAllToken() As SqlToken
    Set getAllToken = New SqlToken
    getAllToken.Init "all", SqlTokenType.KEY_ALL
End Function

' 获取 join 关键字
Private Function getJoinToken() As SqlToken
    Set getJoinToken = New SqlToken
    getJoinToken.Init "join", SqlTokenType.KEY_JOIN
End Function

' 获取 left 关键字
Private Function getLeftToken() As SqlToken
    Set getLeftToken = New SqlToken
    getLeftToken.Init "left", SqlTokenType.KEY_LEFT
End Function

' 获取 right 关键字
Private Function getRightToken() As SqlToken
    Set getRightToken = New SqlToken
    getRightToken.Init "right", SqlTokenType.KEY_RIGHT
End Function

' 获取 inner 关键字
Private Function getInnerToken() As SqlToken
    Set getInnerToken = New SqlToken
    getInnerToken.Init "inner", SqlTokenType.KEY_INNER
End Function

' 获取 cross 关键字
Private Function getCrossToken() As SqlToken
    Set getCrossToken = New SqlToken
    getCrossToken.Init "cross", SqlTokenType.KEY_CROSS
End Function

' 获取 out 关键字
Private Function getOuterToken() As SqlToken
    Set getOuterToken = New SqlToken
    getOuterToken.Init "outer", SqlTokenType.KEY_OUTER
End Function

' 获取 use 关键字
Private Function getUseToken() As SqlToken
    Set getUseToken = New SqlToken
    getUseToken.Init "use", SqlTokenType.KEY_USE
End Function

' 获取 index 关键字
Private Function getIndexToken() As SqlToken
    Set getIndexToken = New SqlToken
    getIndexToken.Init "index", SqlTokenType.KEY_INDEX
End Function

' 获取 ignore 关键字
Private Function getIgnoreToken() As SqlToken
    Set getIgnoreToken = New SqlToken
    getIgnoreToken.Init "ignore", SqlTokenType.KEY_IGNORE
End Function

' 获取 key 关键字
Private Function getKeyToken() As SqlToken
    Set getKeyToken = New SqlToken
    getKeyToken.Init "key", SqlTokenType.KEY_KEY
End Function

' 获取 for 关键字
Private Function getForToken() As SqlToken
    Set getForToken = New SqlToken
    getForToken.Init "for", SqlTokenType.KEY_FOR
End Function

' 获取 force 关键字
Private Function getForceToken() As SqlToken
    Set getForceToken = New SqlToken
    getForceToken.Init "force", SqlTokenType.KEY_FORCE
End Function

' 获取 as 关键字
Private Function getAsToken() As SqlToken
    Set getAsToken = New SqlToken
    getAsToken.Init "as", SqlTokenType.KEY_AS
End Function

' 获取左括号
Private Function getLeftParenthesisToken() As SqlToken
    Set getLeftParenthesisToken = New SqlToken
    getLeftParenthesisToken.Init "(", SqlTokenType.LEFT_PARENTHESIS
End Function

' 获取右括号
Private Function getRightParenthesisToken() As SqlToken
    Set getRightParenthesisToken = New SqlToken
    getRightParenthesisToken.Init ")", SqlTokenType.RIGHT_PARENTHESIS
End Function

' 获取 partition 关键字
Private Function getPartitionToken() As SqlToken
    Set getPartitionToken = New SqlToken
    getPartitionToken.Init "partition", SqlTokenType.KEY_PARTITION
End Function

' 获取逻辑运算符: and
Private Function getAndToken() As SqlToken
    Set getAndToken = New SqlToken
    getAndToken.Init "and", SqlTokenType.KEY_AND
End Function

' 获取一个条件运算符: <=
Private Function getLeConditionOperator() As SqlToken
    Set getLeConditionOperator = New SqlToken
    getLeConditionOperator.Init "<=", SqlTokenType.OPERA_LE
End Function

' 获取一个任意名称的 STR_NAME token
Private Function getStrNameToken(ByRef s As String) As SqlToken
    Set getStrNameToken = New SqlToken
    getStrNameToken.Init s, SqlTokenType.STR_NAME
End Function

' 获取表名 token
Private Function getTableNameToken() As SqlToken
    Set getTableNameToken = New SqlToken
    getTableNameToken.Init TEST_TABLE_NAME, SqlTokenType.STR_NAME
End Function

' 获取 [.] token
Private Function getPointToken() As SqlToken
    Set getPointToken = New SqlToken
    getPointToken.Init TEST_POINT, SqlTokenType.POINT
End Function

' 获取列名 token
Private Function getColNameToken() As SqlToken
    Set getColNameToken = New SqlToken
    getColNameToken.Init TEST_COL_NAME, SqlTokenType.STR_NAME
End Function

' 创建和前一个元素在同一行的注释
Private Function createShareLineCommentToken(ByRef v As String) As SqlToken
    Set createShareLineCommentToken = New SqlToken
    createShareLineCommentToken.Init v, SqlTokenType.SHARE_LINE_COMMENT
End Function

' 获取一个单行的注释
Private Function createSingleLineCommentToken(ByRef v As String) As SqlToken
    Set createSingleLineCommentToken = New SqlToken
    createSingleLineCommentToken.Init v, SqlTokenType.SINGLE_LINE_COMMENT
End Function

' 获取和前一个元素在同一行的注释
Private Function getShareLineCommentToken() As SqlToken
    Set getShareLineCommentToken = New SqlToken
    getShareLineCommentToken.Init TEST_SHARE_LINE_COMMENT, SqlTokenType.SHARE_LINE_COMMENT
End Function

' 获取一个单行的注释
Private Function getSingleLineCommentToken() As SqlToken
    Set getSingleLineCommentToken = New SqlToken
    getSingleLineCommentToken.Init TEST_SINGLE_LINE_COMMENT, SqlTokenType.SINGLE_LINE_COMMENT
End Function

' 获取一个逗号的元素
Private Function getCommaToken() As SqlToken
    Set getCommaToken = New SqlToken
    getCommaToken.Init ",", SqlTokenType.COMMA
End Function

' 获取一个没有边界括号的多行元素
' 多行连接的结果:
' col1 & ","
' col2
Private Function getMultiLineElemNoParenthesis() As ISqlFormatterElem
    Dim g As SqlFormatterCommaDeliMultiElem
    Set g = New SqlFormatterCommaDeliMultiElem

    Dim elem As ISqlFormatterElem
    Set elem = g

    ' 1. 增加一个单行元素
    elem.addComponent getStrNameToken("col1")

    ' 2. 手动设置为多行元素，仍然是单行
    g.setNeedMultiLineWhenHasComma True

    ' 3. 在末尾添加一个逗号元素，仍然是单行的
    elem.addComponent getCommaToken

    ' 4. 再增加一个单行元素，变成多行的
    elem.addComponent getStrNameToken("col2")

    Set getMultiLineElemNoParenthesis = elem
End Function

' 获取一个没有边界括号的多行元素
' 多行连接的结果:
' col1 & ","
' col2 --aaaaaa
Private Function getMultiLineElemNoParenthesisEndWithComment() As ISqlFormatterElem
    Dim g As SqlFormatterCommaDeliMultiElem
    Set g = New SqlFormatterCommaDeliMultiElem

    Dim elem As ISqlFormatterElem
    Set elem = g

    ' 1. 增加一个单行元素
    elem.addComponent getStrNameToken("col1")

    ' 2. 手动设置为多行元素，仍然是单行
    g.setNeedMultiLineWhenHasComma True

    ' 3. 在末尾添加一个逗号元素，仍然是单行的
    elem.addComponent getCommaToken

    ' 4. 再增加一个单行元素，变成多行的
    elem.addComponent getStrNameToken("col2")

    ' 5. 增加一个和前一个元素同一行的注释
    elem.addComponent getShareLineCommentToken

    Set getMultiLineElemNoParenthesisEndWithComment = elem
End Function

' ======================================================================
' 辅助方法测试用例
' ======================================================================


' 测试: 获取Not开头的条件运算符
Private Sub test_getNotConditionOp()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim offset As Integer

    ' ==============================
    ' 1. 扫描器内只有 not (返回 not)
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getNotToken _
    )

    ' 1. 执行测试方法
    Set elem = analyzer.getNotConditionOp(scanner)

    ' 2. 检查结果
    Debug.Assert TypeOf elem Is SqlToken
    Debug.Assert elem.getType = SqlTokenType.KEY_NOT
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: not + 非条件运算符，返回 null
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getNotToken, _
        getLeftParenthesisToken _
    )

    ' 1. 执行测试方法
    Set elem = analyzer.getNotConditionOp(scanner)

    ' 2. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.ActiveIndex = 0

    ' ==============================
    ' 3. 扫描器内: not + 条件运算符
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getNotToken, _
        getLeConditionOperator _
    )

    ' 1. 执行测试方法
    Set elem = analyzer.getNotConditionOp(scanner)

    ' 2. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "not <="
    Debug.Assert elem.getType = SqlTokenType.OPERA_LE

End Sub

' 测试: 为一个token吸收一个同行注释并合并为TokenGroup
Private Sub test_consumeShareLineCommentToTokenGroup()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim offset As Integer
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim tgroup As SqlFormatterTokenGroup

    ' ==============================
    ' 1. 扫描器内是: 空，返回原始 token
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getCommaToken)
    scanner.toNext
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内是: 一个同行注释，测试吸收同行注释
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.STR_NAME
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 3. 扫描器内是: 一个单行注释，测试单行注释无法吸收
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(createSingleLineCommentToken("--c1"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c1"
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT

    ' ==============================
    ' 4. 扫描器内是: 一个STR_NAME ，测试不是同行注释无法吸收
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x2"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x2"
    Debug.Assert token.tokenType = SqlTokenType.STR_NAME

    ' ==============================
    ' 5. 扫描器内是: 两个同行注释，测试只会吸收一个同行注释
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"), createShareLineCommentToken("--c2"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c2"
    Debug.Assert token.tokenType = SqlTokenType.SHARE_LINE_COMMENT

    Debug.Assert elem.getType = SqlTokenType.STR_NAME
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 6. 扫描器内是: 一个同行注释 + STR_NAME，测试只会吸收一个同行注释
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"), getStrNameToken("x2"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x2"
    Debug.Assert token.tokenType = SqlTokenType.STR_NAME

    Debug.Assert elem.getType = SqlTokenType.STR_NAME
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 7. 扫描器内是: 一个同行注释 + STR_NAME，测试一个 sql 关键字在组合后，key 的类型仍然在
    ' ==============================
    Set token = getUnionToken
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"), getStrNameToken("x2"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(token, scanner)
    ' 3. 检查执行结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x2"
    Debug.Assert token.tokenType = SqlTokenType.STR_NAME

    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "union --c1"

    ' ==============================
    ' 8. 扫描器内是: 一个同行注释 + STR_NAME，测试一个 SqlFormatterTokenGroup 在组合后
    '    key 的类型仍然在，并且没有创建新的对象 
    ' ==============================
    Set tgroup = New SqlFormatterTokenGroup
    tgroup.ISqlFormatterElem_addComponent getUnionToken
    tgroup.ISqlFormatterElem_addComponent getAllToken
    tgroup.setType SqlTokenType.KEY_UNION
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"), getStrNameToken("x2"))
    ' 2. 执行测试
    Set elem = analyzer.consumeShareLineCommentToTokenGroup(tgroup, scanner)
    ' 3. 检查执行结果
    Debug.Assert elem Is tgroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x2"
    Debug.Assert token.tokenType = SqlTokenType.STR_NAME

    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "union all --c1"

End Sub

' 测试: 将左括号栈回退到扫描器内
Private Sub test_putBackStackToScanner()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器的下一个元素不是: 左括号 或 注释
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, _
        createShareLineCommentToken("--c2"), _
        getLeftParenthesisToken, _
        getStrNameToken("x1") _
    )

    ' 2. 吸收左括号栈
    Set stack = analyzer.parseStartLeftParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.peek
    Debug.Assert token.Value = "x1"

    ' 4. 执行测试方法
    analyzer.putBackStackToScanner scanner, stack

    ' 5. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.peek
    Debug.Assert token.Value = "--c1"

End Sub

' 测试: 解析开头连续出现的左括号
Private Sub test_parseStartLeftParenthesis()

    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器的下一个元素不是: 左括号 或 注释
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("x1") _
    )

    ' 2. 执行测试方法
    Set stack = analyzer.parseStartLeftParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert stack Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    ' ==============================
    ' 2. 扫描器的只有一个: 左括号
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken _
    )

    ' 2. 执行测试方法
    Set stack = analyzer.parseStartLeftParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 2. 扫描器的只有一个: 注释
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1") _
    )

    ' 2. 执行测试方法
    Set stack = analyzer.parseStartLeftParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.SHARE_LINE_COMMENT

    UtilsPool.releaseObjStack stack


    ' ==============================
    ' 3. 扫描器的只有: 一个注释 + 一个左括号 + 一个注释 + 一个左括号
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, _
        createShareLineCommentToken("--c2"), _
        getLeftParenthesisToken _
    )

    ' 2. 执行测试方法
    Set stack = analyzer.parseStartLeftParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.SHARE_LINE_COMMENT
    Debug.Assert token.Value = "--c2"
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.SHARE_LINE_COMMENT
    Debug.Assert token.Value = "--c1"

    UtilsPool.releaseObjStack stack
    ' ==============================
    ' 4. 扫描器的只有: 一个注释 + 一个右括号 + 一个注释 + 一个右括号 + 一个其他类型的元素
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, _
        createShareLineCommentToken("--c2"), _
        getLeftParenthesisToken, _
        getStrNameToken("x1") _
    )

    ' 2. 执行测试方法
    Set stack = analyzer.parseStartLeftParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.SHARE_LINE_COMMENT
    Debug.Assert token.Value = "--c2"
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.SHARE_LINE_COMMENT
    Debug.Assert token.Value = "--c1"

    UtilsPool.releaseObjStack stack
End Sub

' 测试: 解析开头连续出现的右括号
Private Sub test_parseStartRightParenthesis()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner

    ' ==============================
    ' 1. 空扫描器，返回 Nothing
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init UtilsArrayRef.createRealObjArrayRef

    ' 2. 执行测试方法
    Set elem = analyzer.parseStartRightParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is Nothing

    ' ==============================
    ' 2. 扫描器的下一个元素不是: 右括号 或 注释
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("x1") _
    )

    ' 2. 执行测试方法
    Set elem = analyzer.parseStartRightParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    ' ==============================
    ' 3. 扫描器的只有一个: 右括号
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getRightParenthesisToken _
    )

    ' 2. 执行测试方法
    Set elem = analyzer.parseStartRightParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = ")"

    ' ==============================
    ' 4. 扫描器的只有一个: 注释
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1") _
    )

    ' 2. 执行测试方法
    Set elem = analyzer.parseStartRightParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c1"

    ' ==============================
    ' 4. 扫描器的只有: 一个注释 + 一个右括号 + 一个注释 + 一个右括号
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getRightParenthesisToken, _
        createShareLineCommentToken("--c2"), _
        getRightParenthesisToken _
    )

    ' 2. 执行测试方法
    Set elem = analyzer.parseStartRightParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = ") --c2"
    Debug.Assert strArr.Item(2) = ")"

    ' ==============================
    ' 5. 扫描器的只有: 一个注释 + 一个右括号 + 一个注释 + 一个右括号 + 一个其他类型的元素
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getRightParenthesisToken, _
        createShareLineCommentToken("--c2"), _
        getRightParenthesisToken, _
        getStrNameToken("x1") _
    )

    ' 2. 执行测试方法
    Set elem = analyzer.parseStartRightParenthesis(scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = ") --c2"
    Debug.Assert strArr.Item(2) = ")"

End Sub

' 测试: 合并两个with作用域
Private Sub test_saveWithEnvGroup()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim containEnv As SqlFormatterContainerEnv
    Dim withEnv1 As SqlFormatterWithEnv
    Dim withEnv2 As SqlFormatterWithEnv
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. src 是空，返回 desc
    ' ==============================
    ' 1. 创建第二个 with 作用域
    Set withEnv2 = New SqlFormatterWithEnv

    ' 2. 执行测试方法
    Set elem = analyzer.saveWithEnvGroup(Nothing, withEnv2)

    ' 3. 检查结果
    Debug.Assert elem Is withEnv2

    ' ==============================
    ' 2. desc 是空，返回 src
    ' ==============================
    ' 1. 创建第一个 with 作用域
    Set withEnv1 = New SqlFormatterWithEnv

    ' 2. 执行测试方法
    Set elem = analyzer.saveWithEnvGroup(withEnv1, Nothing)

    ' 3. 检查结果
    Debug.Assert elem Is withEnv1

    ' ==============================
    ' 3. src 是标准的 with 作用域，desc 也是标准 with 作用域
    ' ==============================
    ' 1. 创建第一个 with 作用域
    Set withEnv1 = New SqlFormatterWithEnv
    withEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建第二个 with 作用域
    Set withEnv2 = New SqlFormatterWithEnv
    withEnv2.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.saveWithEnvGroup(withEnv1, withEnv2)

    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert Not elem Is withEnv1
    Debug.Assert Not elem Is withEnv2
    Debug.Assert elem.getType = SqlTokenType.KEY_WITH

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 4. src 是不包含边界括号的容器作用域，desc 也是标准 with 作用域
    ' ==============================
    ' 1. 创建第一个 with 作用域
    Set containEnv = New SqlFormatterContainerEnv
    containEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建第二个 with 作用域
    Set withEnv1 = New SqlFormatterWithEnv
    withEnv1.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.saveWithEnvGroup(containEnv, withEnv1)

    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem Is containEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_WITH

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 5. src 是包含边界括号的容器作用域，desc 也是标准 with 作用域
    ' ==============================
    ' 1. 创建第一个 with 作用域
    Set containEnv = New SqlFormatterContainerEnv
    containEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建第二个 with 作用域
    Set withEnv1 = New SqlFormatterWithEnv
    withEnv1.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.saveWithEnvGroup(containEnv, withEnv2)

    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert Not elem Is containEnv
    Debug.Assert Not elem Is withEnv1
    Debug.Assert elem.getType = SqlTokenType.KEY_WITH

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1"
    Debug.Assert strArr.Item(1) = "x2"
End Sub

' 测试: 合并with和其他类型作用域 (合并成一个容器作用域)
Private Sub test_mergeWithEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim containEnv1 As SqlFormatterContainerEnv
    Dim containEnv2 As SqlFormatterContainerEnv
    Dim withEnv As SqlFormatterWithEnv
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. main 是没有边界括号的容器作用域
    '    合并到 main
    ' ==============================
    ' 1. 创建 main 作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT

    ' 2. 创建 with 作用域
    Set withEnv = New SqlFormatterWithEnv
    withEnv.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.mergeWithEnv(withEnv, containEnv1)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert elem Is containEnv1

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x2"
    Debug.Assert strArr.Item(1) = "x1"

    ' ==============================
    ' 2. main 是有右边界括号的容器作用域
    '    合并到 main
    ' ==============================
    ' 1. 创建 main 作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT
    containEnv1.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 创建 with 作用域
    Set withEnv = New SqlFormatterWithEnv
    withEnv.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.mergeWithEnv(withEnv, containEnv1)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert elem Is containEnv1

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "    x2"
    Debug.Assert strArr.Item(1) = "    x1"
    Debug.Assert strArr.Item(2) = ")"

    ' ==============================
    ' 3. main 是有左边界括号的容器作用域, with 是不包含边界括号的容器作用域
    '    合并到 with
    ' ==============================
    ' 1. 创建 main 作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT
    containEnv1.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建 with 作用域
    Set containEnv2 = New SqlFormatterContainerEnv
    containEnv2.ISqlFormatterElem_addComponent getStrNameToken("x2")
    containEnv2.setType SqlTokenType.KEY_WITH

    ' 3. 执行测试方法
    Set elem = analyzer.mergeWithEnv(containEnv2, containEnv1)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert elem Is containEnv2

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x2"
    Debug.Assert strArr.Item(1) = "(x1"

    ' ==============================
    ' 4. main 是有左边界括号的容器作用域, with 标准的 withEnv
    '    生成新的容器作用域，并合并两个作用域
    ' ==============================
    ' 1. 创建 main 作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT
    containEnv1.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建 with 作用域
    Set withEnv = New SqlFormatterWithEnv
    withEnv.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.mergeWithEnv(withEnv, containEnv1)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert Not elem Is containEnv1
    Debug.Assert Not elem Is withEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x2"
    Debug.Assert strArr.Item(1) = "(x1"

    ' ==============================
    ' 5. main 是有左边界括号的容器作用域, with 是包含边界括号的容器作用域
    '    生成新的容器作用域，并合并两个作用域
    ' ==============================
    ' 1. 创建 main 作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT
    containEnv1.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建 with 作用域
    Set containEnv2 = New SqlFormatterContainerEnv
    containEnv2.ISqlFormatterElem_addComponent getStrNameToken("x2")
    containEnv2.setType SqlTokenType.KEY_WITH
    containEnv2.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 执行测试方法
    Set elem = analyzer.mergeWithEnv(containEnv2, containEnv1)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert Not elem Is containEnv1
    Debug.Assert Not elem Is withEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x2"
    Debug.Assert strArr.Item(1) = "(x1"

End Sub

' 测试: 将两个作用域保存到容器作用域 (合并时，将会利用参数中已有的容器作用域)
Private Sub test_containEnvs()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim containEnv1 As SqlFormatterContainerEnv
    Dim containEnv2 As SqlFormatterContainerEnv
    Dim tableEnv As SqlFormatterTableFactorEnv
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 第一个作用域是容器作用域，并且不存在边界括号
    ' ==============================
    ' 1. 创建第一个容器作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT

    ' 2. 创建第二个非容器作用域
    Set tableEnv = New SqlFormatterTableFactorEnv
    tableEnv.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.containEnvs(containEnv1, tableEnv)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.NO_MEAN
    Debug.Assert elem Is containEnv1

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 2. 第二个作用域是容器作用域，并且不存在边界括号
    ' ==============================
    ' 1. 创建第一个非容器作用域
    Set tableEnv = New SqlFormatterTableFactorEnv
    tableEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建第二个容器作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x2")
    containEnv1.setType SqlTokenType.KEY_SELECT

    ' 3. 执行测试方法
    Set elem = analyzer.containEnvs(tableEnv, containEnv1)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.NO_MEAN
    Debug.Assert elem Is containEnv1

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 3. 第一个是包含边界括号的容器作用域，第二个是不存在边界括号的容器作用域
    ' ==============================
    ' 1. 创建第一个包含边界括号的容器作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT
    containEnv1.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建第二个不存在边界括号的容器作用域
    Set containEnv2 = New SqlFormatterContainerEnv
    containEnv2.ISqlFormatterElem_addComponent getStrNameToken("x2")
    containEnv2.setType SqlTokenType.KEY_WITH

    ' 3. 执行测试方法
    Set elem = analyzer.containEnvs(containEnv1, containEnv2)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.NO_MEAN
    Debug.Assert elem Is containEnv2

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 3. 两个都是是包含边界括号的容器作用域
    ' ==============================
    ' 1. 创建第一个包含边界括号的容器作用域
    Set containEnv1 = New SqlFormatterContainerEnv
    containEnv1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containEnv1.setType SqlTokenType.KEY_SELECT
    containEnv1.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建第二个不存在边界括号的容器作用域
    Set containEnv2 = New SqlFormatterContainerEnv
    containEnv2.ISqlFormatterElem_addComponent getStrNameToken("x2")
    containEnv2.setType SqlTokenType.KEY_WITH
    containEnv2.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 执行测试方法
    Set elem = analyzer.containEnvs(containEnv1, containEnv2)

    ' 4. 检查结果
    Debug.Assert elem.getType = SqlTokenType.NO_MEAN
    Debug.Assert Not elem Is containEnv1
    Debug.Assert Not elem Is containEnv2

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1"
    Debug.Assert strArr.Item(1) = "(x2"
End Sub

' 测试: 生成union关键字
Private Sub test_parseUnionKey()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner

    ' ==============================
    ' 1. 扫描器内只有一个 union
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getUnionToken _
    )

    ' 执行测试方法
    Set elem = analyzer.parseUnionKey(scanner)

    Debug.Assert elem.toString(config) = "union"
    Debug.Assert TypeOf elem Is SqlToken
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内只有一个 union + 另一个 非 all 的元素
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getUnionToken, _
        getStrNameToken("x1") _
    )

    ' 执行测试方法
    Set elem = analyzer.parseUnionKey(scanner)

    Debug.Assert elem.toString(config) = "union"
    Debug.Assert TypeOf elem Is SqlToken

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    ' ==============================
    ' 3. 扫描器内只有 union + all
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getUnionToken, _
        getAllToken _
    )

    ' 执行测试方法
    Set elem = analyzer.parseUnionKey(scanner)

    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert elem.toString(config) = "union all"
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 4. 扫描器内有 union + all + 另一个非 all 的元素
    ' ==============================
    Set scanner = getObjArrayRefScanner( _
        getUnionToken, _
        getAllToken, _
        getStrNameToken("x1") _
    )

    ' 执行测试方法
    Set elem = analyzer.parseUnionKey(scanner)

    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert elem.toString(config) = "union all"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

End Sub

' 测试: 吸收union关键字到作用域
Private Sub test_mergeKeyUnion()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim tableEnv As SqlFormatterTableFactorEnv
    Dim containerEnv As SqlFormatterContainerEnv
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner

    ' ==============================
    ' 1. 作用域的类型不是容器作用域 + 扫描器中只有 union
    ' ==============================
    Set tableEnv = New SqlFormatterTableFactorEnv
    tableEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    Set scanner = getObjArrayRefScanner( _
        getUnionToken _
    )

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, tableEnv)

    ' 检查结果的类型
    Debug.Assert Not elem Is tableEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "union"

    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 没有边界括号的容器作用域 + union
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    Set scanner = getObjArrayRefScanner( _
        getUnionToken _
    )

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, containerEnv)

    ' 检查结果的类型
    Debug.Assert elem Is containerEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "union"

    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 3. 有边界括号的容器作用域 + union
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")
    containerEnv.ISqlFormatterElem_addBoundaryLeftParenthesis

    Set scanner = getObjArrayRefScanner( _
        getUnionToken _
    )

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, containerEnv)

    ' 检查结果的类型
    Debug.Assert Not elem Is containerEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1"
    Debug.Assert strArr.Item(1) = "union"

    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 4. 没有边界括号的容器作用域 + union + 其他非 all 的key
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    Set scanner = getObjArrayRefScanner( _
        getUnionToken, _
        getStrNameToken("c1") _
    )

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, containerEnv)

    ' 检查结果的类型
    Debug.Assert elem Is containerEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "union"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "c1"

    ' ==============================
    ' 5. 没有边界括号的容器作用域 + union + all
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    Set scanner = getObjArrayRefScanner( _
        getUnionToken, _
        getAllToken _
    )

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, containerEnv)

    ' 检查结果的类型
    Debug.Assert elem Is containerEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "union all"

    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 6. 没有边界括号的容器作用域 + union + all + 一个同行注释
    '    测试: 吸收关键字后面的同行注释，并且和 union all 连接到同一行
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    Set scanner = getObjArrayRefScanner(getUnionToken, getAllToken, _
        createShareLineCommentToken("--c1"))

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, containerEnv)

    ' 检查结果的类型
    Debug.Assert elem Is containerEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "union all --c1"

    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 7. 没有边界括号的容器作用域 + union + 一个同行注释 + 其他非 all 的key
    '    测试: 吸收关键字后面的同行注释，并且和 union 连接到同一行
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    Set scanner = getObjArrayRefScanner(getUnionToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("c1"))

    ' 执行测试方法
    Set elem = analyzer.mergeKeyUnion(scanner, containerEnv)

    ' 检查结果的类型
    Debug.Assert elem Is containerEnv
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "union --c1"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "c1"
End Sub

' 测试: 吸收一个token到作用域右侧
Private Sub test_consumeTokenToEnvRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim env As SqlFormatterContainerEnv

    ' ==============================
    ' 1. 无边界括号，可加
    ' ==============================
    ' 1. 初始化添加一个元素
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 吸收一个token到作用域右侧
    Set elem = analyzer.consumeTokenToEnvRight(env, getStrNameToken("x2"))

    Debug.Assert elem Is env
    Debug.Assert elem.hasBoundaryParenthesis = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 2. 只增加左边界括号
    ' ==============================
    ' 1. 初始化添加一个元素 + 左边界括号
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 吸收一个token到作用域右侧
    Set elem = analyzer.consumeTokenToEnvRight(env, getStrNameToken("x2"))

    Debug.Assert Not elem Is env
    Debug.Assert Typeof elem Is SqlFormatterContainerEnv
    Debug.Assert elem.hasBoundaryParenthesis = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 3. 只增加右边界括号
    ' ==============================
    ' 1. 初始化添加一个元素 + 右边界括号
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 吸收一个token到作用域右侧
    Set elem = analyzer.consumeTokenToEnvRight(env, getStrNameToken("x2"))

    Debug.Assert Not elem Is env
    Debug.Assert Typeof elem Is SqlFormatterContainerEnv
    Debug.Assert elem.hasBoundaryParenthesis = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1)"
    Debug.Assert strArr.Item(1) = "x2"

    ' ==============================
    ' 4. 增加左右边界括号
    ' ==============================
    ' 1. 初始化添加一个元素 + 右边界括号
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryLeftParenthesis
    env.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 吸收一个token到作用域右侧
    Set elem = analyzer.consumeTokenToEnvRight(env, getStrNameToken("x2"))

    Debug.Assert Not elem Is env
    Debug.Assert Typeof elem Is SqlFormatterContainerEnv
    Debug.Assert elem.hasBoundaryParenthesis = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1)"
    Debug.Assert strArr.Item(1) = "x2"
End Sub

' 测试: 吸收多余的左括号栈到作用域左侧
Private Sub test_consumeLeftParenthesisStackAllTokenToEnv()

    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim col As SqlFormatterCommaDeliMultiElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 左括号栈为空
    ' ==============================
    ' 1. 创建一个空的栈对象
    Set stack = New ObjStack

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToEnv(col, stack)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Debug.Assert elem.toString(config) = "x1"

    ' ==============================
    ' 2. 元素不存在左括号: 栈中只有一个括号，并且原始列对象不被包装
    ' ==============================
    ' 1. 创建一个栈对象: 压入一个括号 + 一个同行注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToEnv(col, stack)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 3. 元素不存在左括号: 栈中有两个括号，并且原始列对象被包装
    ' ==============================
    ' 1. 创建一个栈对象: 压入一个括号 + 一个同行注释 + 压入一个括号
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToEnv(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    (x1"

    ' ==============================
    ' 3. 元素存在左括号
    ' ==============================
    ' 1. 创建一个栈对象: 压入一个括号 + 一个同行注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")

    ' 2. 创建一个列对象，并设置左括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToEnv(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    (x1"

End Sub

' 测试: 吸收一个同行注释到作用域右侧
Private Sub test_consumeOneShareLineCommentToEnvRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim containerEnv As SqlFormatterContainerEnv

    ' ==============================
    ' 1. 扫描器用完，返回原始 env
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    scanner.toNext
    ' 2. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToEnvRight(token, scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器下一个 token 不是同行注释，返回原始 env
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(createSingleLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToEnvRight(token, scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT

    ' ==============================
    ' 3. 扫描器下一个 token 是同行注释 + 原始 env 不可加
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToEnvRight(token, scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 4. 扫描器下一个 token 是同行注释 + 原始 env 可加
    ' ==============================
    ' 1. 创建一个可加的作用域
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 2. 创建空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"))
    ' 3. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToEnvRight(containerEnv, scanner)
    ' 4. 检查结果
    Debug.Assert elem Is containerEnv
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 --c1"

End Sub

' 测试: 吸收多个元素到作用域右侧
Private Sub test_consumeComponentToEnvRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim containerEnv As SqlFormatterContainerEnv
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. env 是不可加的 + 吸收元素
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x2"),getStrNameToken("x3"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentToEnvRight(token, scanner, 2)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = True
    Debug.Assert scanner.getNext.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"
    Debug.Assert strArr.Item(2) = "x3"

    ' ==============================
    ' 2. env 是可加的 + 吸收元素
    ' ==============================
    Set containerEnv = New SqlFormatterContainerEnv
    containerEnv.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x2"),getStrNameToken("x3"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentToEnvRight(containerEnv, scanner, 2)
    ' 3. 检查结果
    Debug.Assert elem Is containerEnv
    Debug.Assert scanner.hasNext = True
    Debug.Assert scanner.getNext.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "x2"
    Debug.Assert strArr.Item(2) = "x3"

End Sub

' 测试: 保证作用域的可添加性
Private Sub test_ensureComponentAdditivityEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim col As SqlFormatterCommaDeliMultiElem

    ' ==============================
    ' 1. 无边界括号，可加
    ' ==============================
    ' 1. 初始化
    Set col = New SqlFormatterCommaDeliMultiElem

    ' 2. 不添加任何内容测试
    Set elem = analyzer.ensureComponentAdditivityEnv(col)
    Debug.Assert elem Is col
    Debug.Assert elem.hasBoundaryParenthesis = False

    ' 3. 添加一个元素
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    Set elem = analyzer.ensureComponentAdditivityEnv(col)

    Debug.Assert elem Is col
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "x1"

    ' ==============================
    ' 2. 只增加左边界括号
    ' ==============================
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addBoundaryLeftParenthesis
    Set elem = analyzer.ensureComponentAdditivityEnv(col)

    Debug.Assert Typeof elem Is SqlFormatterContainerEnv
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "("

    ' ==============================
    ' 3. 只增加右边界括号
    ' ==============================
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addBoundaryRightParenthesis
    Set elem = analyzer.ensureComponentAdditivityEnv(col)

    Debug.Assert Typeof elem Is SqlFormatterContainerEnv
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = ")"

    ' ==============================
    ' 4. 增加左右边界括号
    ' ==============================
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addBoundaryLeftParenthesis
    col.ISqlFormatterElem_addBoundaryRightParenthesis
    Set elem = analyzer.ensureComponentAdditivityEnv(col)

    Debug.Assert Typeof elem Is SqlFormatterContainerEnv
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "()"
End Sub

' 吸收左括号栈栈顶连续出现的注释到作用域左侧
Public Sub test_consumeLPStackTopCommentToEnvLeft()

    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim env As SqlFormatterContainerEnv
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 使用一个可加的作用域
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Dim stack As ObjStack
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")

    ' 2. 创建一个作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopCommentToEnvLeft(env, stack)

    ' 4. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "x1"

    ' 5. 检查: 栈没有用完，并且下一个是左括号
    Debug.Assert stack.Count = 1
    Set token = stack.pop
    Debug.Assert token.Value = "("

    ' ==============================
    ' 2. 使用一个不可加的作用域
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")

    ' 2. 创建一个作用域，并增加左括号
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopCommentToEnvLeft(env, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is env
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "(x1"

    ' 5. 检查: 扫描器没有用完，并且下一个是左括号
    Debug.Assert stack.Count = 1
    Set token = stack.pop
    Debug.Assert token.Value = "("

End Sub

' 吸收左括号栈栈顶连续出现的单行注释到作用域左侧
Public Sub test_consumeLPStackTopSingleLineCommentToEnvLeft()

    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim env As SqlFormatterContainerEnv
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 使用一个可加的作用域
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")

    ' 2. 创建一个作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopSingleLineCommentToEnvLeft(env, stack)

    ' 4. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "--c2"
    Debug.Assert strArr.Item(1) = "x1"

    ' 5. 检查: 栈没有用完，并且下一个是单行注释
    Debug.Assert stack.Count = 2
    Set token = stack.pop
    Debug.Assert token.Value = "--c1"

    ' ==============================
    ' 2. 使用一个不可加的作用域
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")

    ' 2. 创建一个作用域，并增加左括号
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopSingleLineCommentToEnvLeft(env, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is env
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "--c2"
    Debug.Assert strArr.Item(1) = "(x1"

    ' 5. 检查: 扫描器没有用完，并且下一个是单行注释
    Debug.Assert stack.Count = 2
    Set token = stack.pop
    Debug.Assert token.Value = "--c1"

    ' ==============================
    ' 3. 栈顶不是单行注释
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")

    ' 2. 创建一个作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopSingleLineCommentToEnvLeft(env, stack)

    ' 4. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1"

    ' 5. 检查: 栈没有用完，并且下一个是单行注释
    Debug.Assert stack.Count = 2
    Set token = stack.pop
    Debug.Assert token.Value = "--c1"

    ' ==============================
    ' 4. 吸收多个单行注释
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")
    stack.push createSingleLineCommentToken("--c3")

    ' 2. 创建一个作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopSingleLineCommentToEnvLeft(env, stack)

    ' 4. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c2"
    Debug.Assert strArr.Item(1) = "--c3"
    Debug.Assert strArr.Item(2) = "x1"

    ' 5. 检查: 栈没有用完，并且下一个是单行注释
    Debug.Assert stack.Count = 2
    Set token = stack.pop
    Debug.Assert token.Value = "--c1"

End Sub

' 测试: 吸收右侧连续出现的注释到作用域
Private Sub test_consumeCommentToEnvRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim env As SqlFormatterContainerEnv
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner

    ' ==============================
    ' 1. 使用一个可加的作用域
    ' ==============================
    ' 1. 创建一个作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建扫描器，包含: 两个注释 + 一个右括号
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        createSingleLineCommentToken("--c2"), _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.consumeCommentToEnvRight(scanner, env)

    ' 4. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"

    ' 5. 检查: 栈没有用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 2. 使用一个不可加的作用域
    ' ==============================
    ' 1. 创建一个作用域，并增加右括号
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 创建扫描器，包含: 两个注释 + 一个右括号
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        createSingleLineCommentToken("--c2"), _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.consumeCommentToEnvRight(scanner, env)

    ' 4. 检查结果
    Debug.Assert Not elem Is env
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1) --c1"
    Debug.Assert strArr.Item(1) = "--c2"

    ' 5. 检查: 栈没有用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

End Sub

' 测试: 作用域括号配对
Private Sub test_matchEnvBoundaryParenthesis()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim env As SqlFormatterContainerEnv
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner

    ' ==============================
    ' 1. 同时吸收栈 + 扫描器
    '    测试末尾的同行注释可以吸收，单行注释不会吸收
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 一个注释 + 一个左括号 + 两个注释
    Dim stack As ObjStack
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createSingleLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push createSingleLineCommentToken("--c2")
    stack.push createSingleLineCommentToken("--c3")

    ' 2. 创建一个列对象
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 创建一个扫描器，包含: 一个同行注释 + 一个右括号 + 一个同行注释 + 一个单行注释 + 一个其他token
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c4"), _
        getRightParenthesisToken, _
        createShareLineCommentToken("--c5"), _
        createSingleLineCommentToken("--c6"), _
        getStrNameToken("cx") _
    )

    ' 4. 执行列对象配对
    Set elem = analyzer.matchEnvBoundaryParenthesis(env, scanner, stack)
    ' 5. 检查结果
    Debug.Assert Not elem Is env
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c6"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "("
    Debug.Assert strArr.Item(2) = "    --c2"
    Debug.Assert strArr.Item(3) = "    --c3"
    Debug.Assert strArr.Item(4) = "    x1 --c4"
    Debug.Assert strArr.Item(5) = ") --c5"

    ' ==============================
    ' 2. 没有栈 + 只吸收扫描器开头的注释
    ' ==============================
    ' 1. 创建一个列对象
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建一个扫描器，包含: 一个同行注释 + 一个右括号 + 一个注释 + 一个其他token
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getRightParenthesisToken _
    )

    ' 3. 执行列对象配对
    Set elem = analyzer.matchEnvBoundaryParenthesis(env, scanner, Nothing)

    ' 3. 检查结果
    Debug.Assert elem Is env
    Debug.Assert elem.toString(config) = "x1 --c1"

End Sub

' 测试: 将栈中的所有内容保存到作用域末尾
Private Sub test_appendStackToEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim env As SqlFormatterContainerEnv
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 栈对象是 Nothing
    ' ==============================
    ' 1. 创建作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 执行测试方法
    Set elem = analyzer.appendStackToEnv(env, Nothing)

    ' 3. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1"

    ' ==============================
    ' 2. 可增加的作用域 + 栈[一个括号 + 一个同行注释 + 一个括号 + 一个括号(这里两个括号来标识吸收的方向)]
    ' ==============================
    ' 1. 创建作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建栈对象
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.appendStackToEnv(env, stack)

    ' 4. 检查结果
    Debug.Assert elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "x1"
    Debug.Assert strArr.Item(1) = "( --c1"
    Debug.Assert strArr.Item(2) = "("
    Debug.Assert strArr.Item(3) = "("

    ' ==============================
    ' 3. 不可增加的作用域(左括号) + 栈[一个括号 + 一个同行注释 + 一个括号 + 一个括号(这里两个括号来标识吸收的方向)]
    ' ==============================
    ' 1. 创建作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建栈对象
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.appendStackToEnv(env, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "(x1"
    Debug.Assert strArr.Item(1) = "( --c1"
    Debug.Assert strArr.Item(2) = "("
    Debug.Assert strArr.Item(3) = "("

    ' ==============================
    ' 3. 不可增加的作用域(右括号) + 栈[一个括号 + 一个同行注释 + 一个括号 + 一个括号(这里两个括号来标识吸收的方向)]
    ' ==============================
    ' 1. 创建作用域
    Set env = New SqlFormatterContainerEnv
    env.ISqlFormatterElem_addComponent getStrNameToken("x1")
    env.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 创建栈对象
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.appendStackToEnv(env, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is env
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "x1)"
    Debug.Assert strArr.Item(1) = "( --c1"
    Debug.Assert strArr.Item(2) = "("
    Debug.Assert strArr.Item(3) = "("
End Sub

' 测试: 将栈中的所有内容导出到一个容器作用域
Private Sub test_clearStackToContainerEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 栈对象是 Nothing
    ' ==============================
    Set elem = analyzer.clearStackToContainerEnv(Nothing)

    Debug.Assert elem Is Nothing

    ' ==============================
    ' 2. 使用: 一个括号 + 一个同行注释 + 一个括号
    ' ==============================
    ' 创建栈对象
    Dim stack As ObjStack
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken

    ' 执行测试方法
    Set elem = analyzer.clearStackToContainerEnv(stack)

    ' 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "("

    ' 栈对象被用完
    Debug.Assert stack.Count = 0
End Sub

' 测试: 保证列对象的可添加性
Private Sub test_ensureComponentAdditivityCol()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim col As SqlFormatterCommaDeliMultiElem

    ' ==============================
    ' 1. 无边界括号，可加
    ' ==============================
    ' 1. 初始化
    Set col = New SqlFormatterCommaDeliMultiElem

    ' 2. 不添加任何内容测试
    Set elem = analyzer.ensureComponentAdditivityCol(col)
    Debug.Assert elem Is col
    Debug.Assert elem.hasBoundaryParenthesis = False

    ' 3. 添加一个元素
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    Set elem = analyzer.ensureComponentAdditivityCol(col)

    Debug.Assert elem Is col
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "x1"

    ' ==============================
    ' 2. 只增加左边界括号
    ' ==============================
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addBoundaryLeftParenthesis
    Set elem = analyzer.ensureComponentAdditivityCol(col)

    Debug.Assert Typeof elem Is SqlFormatterContainerCol
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "("

    ' ==============================
    ' 3. 只增加右边界括号
    ' ==============================
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addBoundaryRightParenthesis
    Set elem = analyzer.ensureComponentAdditivityCol(col)

    Debug.Assert Typeof elem Is SqlFormatterContainerCol
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = ")"

    ' ==============================
    ' 4. 增加左右边界括号
    ' ==============================
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addBoundaryLeftParenthesis
    col.ISqlFormatterElem_addBoundaryRightParenthesis
    Set elem = analyzer.ensureComponentAdditivityCol(col)

    Debug.Assert Typeof elem Is SqlFormatterContainerCol
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "()"
End Sub

' 测试: 吸收一个token到列对象右侧
Private Sub test_consumeTokenToColRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim col As SqlFormatterCommaDeliMultiElem

    ' ==============================
    ' 1. 无边界括号，可加
    ' ==============================
    ' 1. 初始化添加一个元素
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 吸收一个token到列对象右侧
    Set elem = analyzer.consumeTokenToColRight(col, getStrNameToken("x2"))

    Debug.Assert elem Is col
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "x1 x2"

    ' ==============================
    ' 2. 只增加左边界括号
    ' ==============================
    ' 1. 初始化添加一个元素 + 左边界括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 吸收一个token到列对象右侧
    Set elem = analyzer.consumeTokenToColRight(col, getStrNameToken("x2"))

    Debug.Assert Typeof elem Is SqlFormatterContainerCol
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "(x1 x2"

    ' ==============================
    ' 3. 只增加右边界括号
    ' ==============================
    ' 1. 初始化添加一个元素 + 右边界括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 吸收一个token到列对象右侧
    Set elem = analyzer.consumeTokenToColRight(col, getStrNameToken("x2"))

    Debug.Assert Typeof elem Is SqlFormatterContainerCol
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "x1) x2"

    ' ==============================
    ' 4. 增加左右边界括号
    ' ==============================
    ' 1. 初始化添加一个元素 + 右边界括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryLeftParenthesis
    col.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 吸收一个token到列对象右侧
    Set elem = analyzer.consumeTokenToColRight(col, getStrNameToken("x2"))

    Debug.Assert Typeof elem Is SqlFormatterContainerCol
    Debug.Assert elem.hasBoundaryParenthesis = False
    Debug.Assert elem.toString(config) = "(x1) x2"
End Sub

' 测试: 吸收一个同行注释到列对象右侧
Private Sub test_consumeOneShareLineCommentToColRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim containerCol As SqlFormatterContainerCol

    ' ==============================
    ' 1. 扫描器用完，返回原始 env
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    scanner.toNext
    ' 2. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToColRight(token, scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器下一个 token 不是同行注释，返回原始 env
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(createSingleLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToColRight(token, scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT

    ' ==============================
    ' 3. 扫描器下一个 token 是同行注释 + 原始 env 不可加
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToColRight(token, scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 4. 扫描器下一个 token 是同行注释 + 原始 env 可加
    ' ==============================
    ' 1. 创建一个可加的作用域
    Set containerCol = New SqlFormatterContainerCol
    containerCol.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 2. 创建空扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"))
    ' 3. 执行测试方法
    Set elem = analyzer.consumeOneShareLineCommentToColRight(containerCol, scanner)
    ' 4. 检查结果
    Debug.Assert elem Is containerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 --c1"

End Sub

' 测试: 吸收多个元素到列对象右侧
Private Sub test_consumeComponentToColRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim containerCol As SqlFormatterContainerCol
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. env 是不可加的 + 吸收元素
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x2"),getStrNameToken("x3"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentToColRight(token, scanner, 2)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Debug.Assert scanner.getNext.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1 x2 x3"

    ' ==============================
    ' 2. env 是可加的 + 吸收元素
    ' ==============================
    Set containerCol = New SqlFormatterContainerCol
    containerCol.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 1. 创建空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x2"),getStrNameToken("x3"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentToColRight(containerCol, scanner, 2)
    ' 3. 检查结果
    Debug.Assert elem Is containerCol
    Debug.Assert scanner.hasNext = True
    Debug.Assert scanner.getNext.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1 x2 x3"

End Sub

' 测试: 从左括号栈吸收多个元素到列对象左侧
Private Sub test_consumeComponentFromStackToColLeft()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim containerCol As SqlFormatterContainerCol
    Dim stack As ObjStack

    ' ==============================
    ' 1. 一个 token + 栈内: 一个注释
    '    测试: 只吸收一个注释后，返回结果的类型是容器
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建栈对象
    Set stack = New ObjStack
    stack.push createSingleLineCommentToken("--c1")
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentFromStackToColLeft(token, stack, 1)
    ' 3. 检查结果
    Debug.Assert stack.Count = 0
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.item(0) = "--c1"
    Debug.Assert strArr.item(1) = "x1"

    ' ==============================
    ' 2. 一个 token + 栈内: 两个注释
    '    测试: 只吸收多个注释后，返回结果的类型是容器
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建栈对象
    Set stack = New ObjStack
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentFromStackToColLeft(token, stack, 2)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert stack.Count = 0

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.item(0) = "--c1"
    Debug.Assert strArr.item(1) = "--c2"
    Debug.Assert strArr.item(2) = "x1"

    ' ==============================
    ' 3. 一个 token + 栈内: 两个注释
    '    测试: 只吸收一个注释后，返回结果的类型是容器
    ' ==============================
    Set token = getStrNameToken("x1")
    ' 1. 创建栈对象
    Set stack = New ObjStack
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentFromStackToColLeft(token, stack, 1)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert stack.Count = 1

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.item(0) = "--c2"
    Debug.Assert strArr.item(1) = "x1"

    ' ==============================
    ' 4. 一个 容器列 + 栈内: 两个注释
    '    测试: 注释被吸收到元素列对象
    ' ==============================
    Set containerCol = New SqlFormatterContainerCol
    containerCol.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 1. 创建栈对象
    Set stack = New ObjStack
    stack.push createSingleLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")
    ' 2. 执行测试方法
    Set elem = analyzer.consumeComponentFromStackToColLeft(token, stack, 2)
    ' 3. 检查结果
    Set strArr = elem.toStringArray(config)
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.item(0) = "--c1"
    Debug.Assert strArr.item(1) = "--c2"
    Debug.Assert strArr.item(2) = "x1"
    Debug.Assert stack.Count = 0

End Sub

' 吸收左括号栈栈顶连续出现的注释到列对象左侧
Private Sub test_consumeLPStackTopCommentToColLeft()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim col As SqlFormatterCommaDeliMultiElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 使用一个可加的列对象
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Dim stack As ObjStack
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopCommentToColLeft(col, stack)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "x1"

    ' 5. 检查: 栈没有用完，并且下一个是左括号
    Debug.Assert stack.Count = 1
    Set token = stack.pop
    Debug.Assert token.Value = "("

    ' ==============================
    ' 2. 使用一个不可加的列对象
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push createSingleLineCommentToken("--c2")

    ' 2. 创建一个列对象，并增加左括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 吸收栈内的注释到左侧
    Set elem = analyzer.consumeLPStackTopCommentToColLeft(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "(x1"

    ' 5. 检查: 扫描器没有用完，并且下一个是左括号
    Debug.Assert stack.Count = 1
    Set token = stack.pop
    Debug.Assert token.Value = "("

End Sub

' 测试: 吸收右侧连续出现的注释到列对象
Private Sub test_consumeCommentToColRight()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim col As SqlFormatterCommaDeliMultiElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner

    ' ==============================
    ' 1. 使用一个可加的列对象
    ' ==============================
    ' 1. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建扫描器，包含: 两个注释 + 一个右括号
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        createSingleLineCommentToken("--c2"), _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.consumeCommentToColRight(scanner, col)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"

    ' 5. 检查: 栈没有用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 2. 使用一个不可加的列对象
    ' ==============================
    ' 1. 创建一个列对象，并增加右括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 创建扫描器，包含: 两个注释 + 一个右括号
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        createSingleLineCommentToken("--c2"), _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.consumeCommentToColRight(scanner, col)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1) --c1"
    Debug.Assert strArr.Item(1) = "--c2"

    ' 5. 检查: 栈没有用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

End Sub

' 测试: 对一个右括号进行配对并生成容器列对象
Private Sub test_matchOneRightParenthesis()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner
    Dim stack As ObjStack

    ' ==============================
    ' 1. 左括号栈中不存在左括号
    ' ==============================
    ' 1. 创建一个不包含左括号的括号栈
    Set stack = New ObjStack
    stack.push createSingleLineCommentToken("--c1")

    ' 2. 创建一个扫描器，包含: 一个右括号
    Set scanner = getObjArrayRefScanner( _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.matchOneRightParenthesis(scanner, stack)

    ' 4. 检查结果
    Debug.Assert elem Is Nothing

    ' ==============================
    ' 2. 左括号栈中只存在左括号
    ' ==============================
    ' 1. 创建一个只包含左括号的括号栈
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken

    ' 2. 创建一个扫描器，包含: 一个右括号
    Set scanner = getObjArrayRefScanner( _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.matchOneRightParenthesis(scanner, stack)

    ' 4. 检查结果
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "()"

    Debug.Assert stack.Count = 0

    ' ==============================
    ' 3. 左括号栈中存在左括号 + 注释
    ' ==============================
    ' 1. 创建一个只包含左括号的括号栈
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")

    ' 2. 创建一个扫描器，包含: 一个右括号
    Set scanner = getObjArrayRefScanner( _
        getRightParenthesisToken _
    )

    ' 3. 执行测试方法
    Set elem = analyzer.matchOneRightParenthesis(scanner, stack)

    ' 4. 检查结果
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = ")"

    Debug.Assert stack.Count = 0
End Sub

' 测试: 列对象括号配对
Private Sub test_matchColBoundaryParenthesis()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim col As SqlFormatterCommaDeliMultiElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner
    Dim stack As ObjStack

    ' ==============================
    ' 1. 同时吸收栈 + 扫描器
    '    测试末尾的同行注释可以吸收，单行注释不会吸收
    '    左括号栈中多余的注释不会被吸收
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 一个注释 + 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createSingleLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push createSingleLineCommentToken("--c2")
    stack.push createSingleLineCommentToken("--c3")

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 创建一个扫描器，包含: 一个同行注释 + 一个右括号 + 一个同行注释 + 一个单行注释 + 一个其他token
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c4"), _
        getRightParenthesisToken, _
        createShareLineCommentToken("--c5"), _
        createSingleLineCommentToken("--c6"), _
        getStrNameToken("cx") _
    )

    ' 4. 执行列对象配对
    Set elem = analyzer.matchColBoundaryParenthesis(col, scanner, stack)

    ' 5. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c6"

    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c1"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "("
    Debug.Assert strArr.Item(1) = "    --c2"
    Debug.Assert strArr.Item(2) = "    --c3"
    Debug.Assert strArr.Item(3) = "    x1 --c4"
    Debug.Assert strArr.Item(4) = ") --c5"

    ' ==============================
    ' 2. 没有栈 + 只吸收扫描器开头的注释
    ' ==============================
    ' 1. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建一个扫描器，包含: 一个同行注释 + 一个右括号 + 一个注释 + 一个其他token
    Set scanner = getObjArrayRefScanner( _
        createShareLineCommentToken("--c1"), _
        getRightParenthesisToken _
    )

    ' 3. 执行列对象配对
    Set elem = analyzer.matchColBoundaryParenthesis(col, scanner, Nothing)

    ' 3. 检查结果
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 3. SqlToken + 同时吸收栈 + 扫描器的一个右括号
    ' ==============================
    ' 1. 向栈对象中压入: 一个左括号 + 一个注释 + 一个左括号 + 两个注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 创建一个扫描器，包含: 一个同行注释 + 一个右括号 + 一个注释 + 一个其他token
    Set scanner = getObjArrayRefScanner(getRightParenthesisToken)

    ' 4. 执行列对象配对
    Set elem = analyzer.matchColBoundaryParenthesis(col, scanner, stack)

    ' 5. 检查结果
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "(x1)"

End Sub

' 测试: 吸收多余的左括号栈到列对象左侧
Private Sub test_consumeLeftParenthesisStackAllTokenToCol()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    Dim col As SqlFormatterCommaDeliMultiElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim scanner As ObjArrayRefScanner
    Dim stack As ObjStack

    ' ==============================
    ' 1. 左括号栈为空
    ' ==============================
    ' 1. 创建一个空的栈对象
    Set stack = New ObjStack

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Debug.Assert elem.toString(config) = "x1"

    ' ==============================
    ' 2. 元素不存在左括号: 栈中只有一个括号，并且原始列对象不被包装
    ' ==============================
    ' 1. 创建一个栈对象: 压入一个括号 + 一个同行注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 3. 元素不存在左括号: 栈中有两个括号，并且原始列对象被包装
    ' ==============================
    ' 1. 创建一个栈对象: 压入一个括号 + 一个同行注释 + 压入一个括号
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken

    ' 2. 创建一个列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    (x1"

    ' ==============================
    ' 3. 元素存在左括号
    ' ==============================
    ' 1. 创建一个栈对象: 压入一个括号 + 一个同行注释
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")

    ' 2. 创建一个列对象，并设置左括号
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 3. 执行测试方法
    Set elem = analyzer.consumeLeftParenthesisStackAllTokenToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    (x1"

End Sub

' 测试: 将栈中的所有内容保存到列对象末尾
Private Sub test_appendStackToCol()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim col As SqlFormatterCommaDeliMultiElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 栈对象是 Nothing
    ' ==============================
    ' 1. 创建列对象
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 执行测试方法
    Set elem = analyzer.appendStackToCol(col, Nothing)

    ' 3. 检查结果
    Debug.Assert elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1"

    ' ==============================
    ' 2. 可增加的作用域 + 栈[一个括号 + 一个同行注释 + 一个括号 + 一个括号(这里两个括号来标识吸收的方向)]
    ' ==============================
    ' 1. 创建作用域
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 2. 创建栈对象
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.appendStackToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert elem Is col
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1 ( --c1" ' 这里 x1 不会被识别为函数名
    Debug.Assert strArr.Item(1) = "(("

    ' ==============================
    ' 3. 不可增加的作用域(左括号) + 栈[一个括号 + 一个同行注释 + 一个括号 + 一个括号(这里两个括号来标识吸收的方向)]
    ' ==============================
    ' 1. 创建作用域
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryLeftParenthesis

    ' 2. 创建栈对象
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.appendStackToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(x1 ( --c1"
    Debug.Assert strArr.Item(1) = "(("

    ' ==============================
    ' 3. 不可增加的作用域(右括号) + 栈[一个括号 + 一个同行注释 + 一个括号 + 一个括号(这里两个括号来标识吸收的方向)]
    ' ==============================
    ' 1. 创建作用域
    Set col = New SqlFormatterCommaDeliMultiElem
    col.ISqlFormatterElem_addComponent getStrNameToken("x1")
    col.ISqlFormatterElem_addBoundaryRightParenthesis

    ' 2. 创建栈对象
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.appendStackToCol(col, stack)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1) ( --c1"
    Debug.Assert strArr.Item(1) = "(("
End Sub

' 测试: 将栈中的所有内容导出到一个列对象
Private Sub test_clearStackToContainerCol()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 栈对象是 Nothing
    ' ==============================
    Set elem = analyzer.clearStackToContainerCol(Nothing)

    Debug.Assert elem Is Nothing

    ' ==============================
    ' 2. 使用: 一个括号 + 一个同行注释 + 一个括号
    ' ==============================
    ' 创建栈对象
    Dim stack As ObjStack
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken

    ' 执行测试方法
    Set elem = analyzer.clearStackToContainerCol(stack)

    ' 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "("

    ' 栈对象被用完
    Debug.Assert stack.Count = 0
End Sub

' 测试: 将两个元素合并为多条件组合对象
Private Sub test_mergeTwoElemToMultiConditions()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim multiConditions As SqlFormatterMultiConditions
    Dim condition1 As SqlFormatterTwoElemCondition
    Dim condition2 As SqlFormatterTwoElemCondition

    ' ==============================
    ' 1. core 是多条件组合对象
    ' ==============================
    ' 1. 创建条件组合对象，并保存第一个条件
    Set multiConditions = New SqlFormatterMultiConditions

    Set condition1 = New SqlFormatterTwoElemCondition
    ' 设置第一个元素
    condition1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 设置条件运算符 <=
    condition1.ISqlFormatterCondition_setConditionOperator getLeConditionOperator
    ' 设置第二个元素
    condition1.ISqlFormatterElem_addComponent getStrNameToken("x2")

    multiConditions.addCondition condition1

    ' 2. 创建第二个条件
    Set condition2 = New SqlFormatterTwoElemCondition
    ' 设置逻辑运算符
    condition2.ISqlFormatterElem_addComponent getAndToken
    ' 设置第一个元素
    condition2.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 设置条件运算符 <=
    condition2.ISqlFormatterCondition_setConditionOperator getLeConditionOperator
    ' 设置第二个元素
    condition2.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.mergeTwoElemToMultiConditions(multiConditions, condition2)

    ' 4. 检查结果
    Debug.Assert elem Is multiConditions

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1 <= x2"
    Debug.Assert strArr.Item(1) = "and x1 <= x2"

    ' ==============================
    ' 2. core 是条件对象
    ' ==============================
    Set condition1 = New SqlFormatterTwoElemCondition
    ' 设置第一个元素
    condition1.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 设置条件运算符 <=
    condition1.ISqlFormatterCondition_setConditionOperator getLeConditionOperator
    ' 设置第二个元素
    condition1.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 2. 创建第二个条件
    Set condition2 = New SqlFormatterTwoElemCondition
    ' 设置逻辑运算符
    condition2.ISqlFormatterElem_addComponent getAndToken
    ' 设置第一个元素
    condition2.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 设置条件运算符 <=
    condition2.ISqlFormatterCondition_setConditionOperator getLeConditionOperator
    ' 设置第二个元素
    condition2.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 3. 执行测试方法
    Set elem = analyzer.mergeTwoElemToMultiConditions(condition1, condition2)

    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterMultiConditions
    Debug.Assert Not elem Is condition1
    Debug.Assert Not elem Is condition2

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1 <= x2"
    Debug.Assert strArr.Item(1) = "and x1 <= x2"

    ' ==============================
    ' 3. core 是 null
    ' ==============================
    ' 1. 创建第二个条件
    Set condition2 = New SqlFormatterTwoElemCondition
    ' 设置逻辑运算符
    condition2.ISqlFormatterElem_addComponent getAndToken
    ' 设置第一个元素
    condition2.ISqlFormatterElem_addComponent getStrNameToken("x1")
    ' 设置条件运算符 <=
    condition2.ISqlFormatterCondition_setConditionOperator getLeConditionOperator
    ' 设置第二个元素
    condition2.ISqlFormatterElem_addComponent getStrNameToken("x2")

    ' 2. 执行测试方法
    Set elem = analyzer.mergeTwoElemToMultiConditions(Nothing, condition2)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterMultiConditions
    Debug.Assert Not elem Is condition1
    Debug.Assert Not elem Is condition2

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "and x1 <= x2"

End Sub

' 测试: 创建一个只有逻辑运算符和第一个元素的的条件
Private Sub test_createOneElemCondition()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    ' ==============================
    ' 1. 执行测试
    ' ==============================
    Set elem = analyzer.createOneElemCondition(getAndToken, getStrNameToken("x1"))

    ' 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTwoElemCondition
    Debug.Assert elem.toString(config) = "and x1"
End Sub

' 测试: 创建一个IS_NOT组合关键字
Private Sub test_createIsNotConditionOp()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim elem As ISqlFormatterElem

    ' ==============================
    ' 1. 执行测试
    ' ==============================
    Set elem = analyzer.createIsNotConditionOp(getIsToken, getNotToken)

    ' 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert elem.toString(config) = "is not"
    Debug.Assert elem.getType = SqlTokenType.KEY_IS
End Sub